How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about. | |||
---|---|---|---|
1. Introduction | 2. The requirements | 3. Test case analysis I | 4. Test case analysis II |
Test case analysis III | 6. Test case analysis IV | 7. Setting up a project with Spring Boot | 8. Which tests to write? |
9. API design |
Finally! Actual TDD and code and Spring!
Well, we’ll start with Spring first. But first a reminder.
We’ve already constrained ourselves with the architecture. This is going to be a web based, REST services based Spring application. In the general case, the architecture should support building the app in the most reliable, quick and cheap way. In our case, it’s because I want to improve my Spring skillz.
If you want to follow up, I’ll be posting the code on github. I’m going to have a single project, and tag each version of with the post’s name and date. For example, this post is tagged 1.0.
Generation S
The first thing I’ll do is create a project in Spring Boot. It’s the quickest way to set up a Spring based project these days (I used version SpringBoot 2.0).
What needs to go into our project setup? We’ll select a maven project with the following metadata:
Group: tddWithSpring
Artifact: calculatorDisplay
I’m selecting two dependencies for the generation of the project:
- Web – Obviously, we’re building a web app.
- JPA – For database access.
- H2 – In memory database for our integration tests
JPA and H2 are not needed to begin with, since if you recall, we don’t have any persistency requirements yet. We can start without them and add them later, based on our needs. But since I’m running the show, we’ll add both dependencies now. (One of the other thing I did is added and commented out properties for those two in the application.properties file under resources).
Now, let’s press the “Generate Project” button, and… don’t you love technology? We’ve got a starter project. The regular Maven setup includes source files and tests.
But we still need to do some small modification and cleanups to the project before we start
Remove all clutter
First, let’s remove the mvnw files that were created as part of the project generation. We don’t need them. Let that be a lesson to you – not everything that can be generated is actually good for you. .
The generated project already contains a CalculatorDisplayApp class, and an empty CalculatorDisplayApplicationTests test class. Note that this class is for integration tests – you can see it annotated with @SpringBootTest and @RunWith(SpringTestRunner.class). I’ve renamed the class IntegrationTests for now.
Let’s run the integration test (which I’ve renamed also), the one that was generated:
@Test public void canRunIntegrationTests() { }
The empty test passes, imagine that. But there’s a whole lot of logs going on while it’s running. These Spring logs maybe helpful (maybe), but for us, it’s just a distraction. Digging around StackOverflow, I found the solution to remove all logging below ERROR level (that’s unneeded DEBUG and INFO lines). It involves adding a logback-test.xml file to the test/java/resources folder. Each line tells the minimal logging for each package. For example:
<logger name="org.springframework.boot.test" level="error" />
That’s a lot less noise on the console. With those in, we’ve minimized the logging to something more acceptable.
Run, test, run!
The first thing I usually do to make sure a system runs is to fail a test and then pass it. I do it with the integration test (I’ve added assertTrue(false) and then change it to true). The test runs as expected.
However, running this single test clocks in between 11-15 seconds from the command line (using “mvn test“, not including compilation). Interestingly enough, running the same test from within Eclipse (“Run as JUnit Test“) takes around 5-7 seconds. (Times are on my laptop, YMMV).
That tells us a lot about the overhead of running the Spring framework, and also about Maven. The more complex our software gets, we’ll see this overhead grow, and that’s bad for our feedback cycle. We need integration tests, but for our TDD purposes, we’ll need quick running unit tests as well.
Here’s a thought: If I’m already in learning mode, why not use JUnit 5? Sure, why not. Add the following to the POM file:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.0.1</version> <scope>test</scope> </dependency>
To check that JUnit works, I’ve added a regular test class (unsurprisingly called UnitTests), with a small test (same as before). This is a regular JUnit test, and no @RunWith annotation:
@Test public void canRunUnitTests() { assertTrue(true); }
This test alone, runs in Eclipse in less than 50 micro-seconds. That’s a very good feedback time.
However, when we run it with maven (“mvn test“), which runs both integration and unit tests, we get again to 10-15 seconds. To run just the unit test, we can run Maven with a specific test class, just the UnitTests class:
mvn -Dtest=UnitTests test
Hmmm. This brings the time to around 5-7 seconds. Still not micro-feedback, but a lot better, without getting the whole Spring framework grinding us to a halt.
So, what have we learned?
- We have a project ready for work, that was sort-of easy to set up.
- We know our tests are running.
- We know that integration tests that need Spring have a big overhead to do nothing. We also learned that running tests through Maven adds another overhead that we might want to work around.
Next up: Deciding which tests to write.
Code for this post is here.
4 Comments
Brad · December 10, 2020 at 9:13 pm
Hi!
Thanks for writing this series. I’m not finding enough info here in your post to calm down the logging. Just adding “ to a `logback-test.xml” file results in an error.
Could you perhaps link to the StackOverflow answer you reference?
Brad · December 10, 2020 at 9:17 pm
I’d say still link to the file, but I discovered that what’s missing from the instructions is to wrap the element with a set of tags. Then it works great.
References
http://logback.qos.ch/manual/configuration.html#loggerElement
Gil Zilberfeld · December 11, 2020 at 12:33 pm
Excellent!
Gil Zilberfeld · December 11, 2020 at 12:33 pm
Hey Brad,
Thanks for feedback. There’s no direct link, I wrote that a while ago. Lookup logback questions, and eventually you need to keep entries to the logback file, package by package, so you can remove all the noise.
Gil